/*
 * Project: Sudoku Explainer
 * Copyright (C) 2006-2007 Nicolas Juillerat
 * 
 * Project: Sudoku Hint Engine for Android
 * Copyright (C) 2010-2013 Emanuel Moecklin
 * 
 * Available under the terms of the Lesser General Public License (LGPL)
 */
package com.onegravity.sudoku.solver.rules.hidden;

import java.util.Collection;
import java.util.Collections;

import com.onegravity.sudoku.solver.HintResource;
import com.onegravity.sudoku.solver.R;
import com.onegravity.sudoku.solver.accumulator.HintsAccumulator;
import com.onegravity.sudoku.solver.accumulator.StopHintProductionException;
import com.onegravity.sudoku.solver.core.DirectHintProducer;
import com.onegravity.sudoku.solver.data.Block;
import com.onegravity.sudoku.solver.data.Cell;
import com.onegravity.sudoku.solver.data.Column;
import com.onegravity.sudoku.solver.data.ExtraRegionExtended;
import com.onegravity.sudoku.solver.data.ExtraRegionType;
import com.onegravity.sudoku.solver.data.Grid;
import com.onegravity.sudoku.solver.data.Region;
import com.onegravity.sudoku.solver.data.Row;
import com.onegravity.sudoku.solver.util.SudokuValidator;

/**
 * Implementation of the Hidden Single solving technique (+ full house).
 */
public class HiddenSingleProducer implements DirectHintProducer {

	private SudokuValidator validator;
	private boolean mUseFullHouse;
	private boolean mUseHiddenSingle;

	public HiddenSingleProducer(boolean useFullHouse, boolean useHiddenSingle) {
		mUseFullHouse = useFullHouse;
		mUseHiddenSingle = useHiddenSingle;
	}
	
	@Override
	public synchronized void getHints(Grid grid, HintsAccumulator accu) throws StopHintProductionException, InterruptedException {
		if (!mUseFullHouse && !mUseHiddenSingle) return; 

		validator = new SudokuValidator(grid);

        // check for full houses & hidden singles
        if (grid.hasExtraRegions() && !grid.getExtraRegionType().equals(ExtraRegionType.COLOR)) {
        	// for puzzles with color extra regions we check for hidden singles in blocks, columns and rows first
        	getHints(grid, ExtraRegionExtended.class, accu);
        }
        getHints(grid, Block.class, accu);
        getHints(grid, Column.class, accu);
        getHints(grid, Row.class, accu);
        if (grid.hasExtraRegions() && grid.getExtraRegionType().equals(ExtraRegionType.COLOR)) {
        	getHints(grid, ExtraRegionExtended.class, accu);
        }
    }

    /**
     * For each parts of the given type, check if a value has only one
     * possible potential position.
     * @param regionType the type of the parts to check
     * @throws InterruptedException 
     */  
    private <T extends Region> void getHints(Grid grid, Class<T> regionType, HintsAccumulator accu) throws StopHintProductionException, InterruptedException {

    	// iterate over all regions of a certain type 
    	for (Region region : grid.getRegions(regionType)) {
            if (Thread.interrupted()) throw new InterruptedException();
            boolean isAlone = (region.getEmptyCellCount()==1);

            // iterate over all 9 values
            for (int value=1; value<=9; value++) {
            	
                Cell theCell = null;
                Collection<Cell> selectedCells = null;

                // if we have pencil marks we can work with those to find hidden singles and full houses
                if (region.getNrOfPotentialPositions(value)==1) {
                    // only one potential position -> hidden single or full house (or naked single)
                	int uniqueIndex = region.getPosOfPotentialPositions(value);
                	theCell = region.getCell(uniqueIndex);
                    selectedCells = retrieveSelectedCells(grid, theCell, value, theCell.getPotentialValues().cardinality(), isAlone);
                }

                // if there are no pencil marks we find the hidden singles and full houses by computing our own candidates using the validator 
                else {
                	// count the potential positions of a digit
                	int counter = 0;
                	for (Cell cell:region.getCellSet()) {
                		if (cell.isEmpty() && validator.checkValue(cell.getX(), cell.getY(), value)) { 
                    		if (counter++==1) break;
                			theCell = cell;
                		}
                	}
                	if (counter==1) {
                        // only one potential position -> hidden single or full house (or naked single)
                        selectedCells = retrieveSelectedCells(grid, theCell, value, validator.getCandidates(theCell.getX(), theCell.getY()).cardinality(), isAlone);
                	}
            	}

                if (theCell!=null && selectedCells!=null) {
                	// hidden single or full house
                	if ((mUseFullHouse && isAlone) || (mUseHiddenSingle && !isAlone)) {
                    	accu.add(new HiddenSingleHint(region, theCell, value, isAlone, selectedCells));
                	}
                }
                	
            } // end for (int value=1; value<=9; value++) {
            
        } // end for (Grid.Region region : grid.getRegions(regionType))
    	
    }

    private Collection<Cell> retrieveSelectedCells(Grid grid, Cell cell, int value, int cardinality, boolean isAlone) {
        Collection<Cell> selectedCells = null;
        if (isAlone) {
        	// full house
			selectedCells = Collections.singletonList(cell);
		}
		else if (cardinality>1) {
			// hidden single
			selectedCells = grid.findDigits(value);
		} // else naked single which we will deal with in another producer
        return selectedCells;
    }

    @Override
	public String getProducerName() {
		return HintResource.getString(R.string.producer_hidden_singles);
	}
}